// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/common/origin_trials/trial_token_validator.h"

#include "base/feature_list.h"
#include "base/memory/ptr_util.h"
#include "base/time/time.h"
#include "content/common/origin_trials/trial_token.h"
#include "content/public/common/content_client.h"
#include "content/public/common/content_features.h"
#include "content/public/common/origin_trial_policy.h"
#include "content/public/common/origin_util.h"
#include "net/http/http_response_headers.h"
#include "net/url_request/url_request.h"
#include "third_party/WebKit/public/platform/WebOriginTrialTokenStatus.h"

namespace content {

blink::WebOriginTrialTokenStatus TrialTokenValidator::ValidateToken(
    const std::string& token,
    const url::Origin& origin,
    std::string* feature_name)
{
    ContentClient* content_client = GetContentClient();
    const OriginTrialPolicy* origin_trial_policy = content_client->GetOriginTrialPolicy();
    if (!origin_trial_policy)
        return blink::WebOriginTrialTokenStatus::NotSupported;

    // TODO(iclelland): Allow for multiple signing keys, and iterate over all
    // active keys here. https://crbug.com/543220
    base::StringPiece public_key = origin_trial_policy->GetPublicKey();
    if (public_key.empty())
        return blink::WebOriginTrialTokenStatus::NotSupported;

    blink::WebOriginTrialTokenStatus status;
    std::unique_ptr<TrialToken> trial_token = TrialToken::From(token, public_key, &status);
    if (status != blink::WebOriginTrialTokenStatus::Success)
        return status;

    status = trial_token->IsValid(origin, base::Time::Now());
    if (status != blink::WebOriginTrialTokenStatus::Success)
        return status;

    if (origin_trial_policy->IsFeatureDisabled(trial_token->feature_name()))
        return blink::WebOriginTrialTokenStatus::FeatureDisabled;

    *feature_name = trial_token->feature_name();
    return blink::WebOriginTrialTokenStatus::Success;
}

bool TrialTokenValidator::RequestEnablesFeature(
    const net::URLRequest* request,
    base::StringPiece feature_name)
{
    // TODO(mek): Possibly cache the features that are availble for request in
    // UserData associated with the request.
    return RequestEnablesFeature(request->url(), request->response_headers(),
        feature_name);
}

bool TrialTokenValidator::RequestEnablesFeature(
    const GURL& request_url,
    const net::HttpResponseHeaders* response_headers,
    base::StringPiece feature_name)
{
    if (!base::FeatureList::IsEnabled(features::kOriginTrials))
        return false;

    if (!IsOriginSecure(request_url))
        return false;

    url::Origin origin(request_url);
    size_t iter = 0;
    std::string token;
    while (response_headers->EnumerateHeader(&iter, "Origin-Trial", &token)) {
        std::string token_feature;
        // TODO(mek): Log the validation errors to histograms?
        if (ValidateToken(token, origin, &token_feature) == blink::WebOriginTrialTokenStatus::Success)
            if (token_feature == feature_name)
                return true;
    }
    return false;
}

std::unique_ptr<TrialTokenValidator::FeatureToTokensMap>
TrialTokenValidator::GetValidTokensFromHeaders(
    const url::Origin& origin,
    const net::HttpResponseHeaders* headers)
{
    std::unique_ptr<FeatureToTokensMap> tokens(
        base::MakeUnique<FeatureToTokensMap>());
    if (!base::FeatureList::IsEnabled(features::kOriginTrials))
        return tokens;

    if (!IsOriginSecure(origin.GetURL()))
        return tokens;

    size_t iter = 0;
    std::string token;
    while (headers->EnumerateHeader(&iter, "Origin-Trial", &token)) {
        std::string token_feature;
        if (TrialTokenValidator::ValidateToken(token, origin, &token_feature) == blink::WebOriginTrialTokenStatus::Success) {
            (*tokens)[token_feature].push_back(token);
        }
    }
    return tokens;
}

std::unique_ptr<TrialTokenValidator::FeatureToTokensMap>
TrialTokenValidator::GetValidTokens(const url::Origin& origin,
    const FeatureToTokensMap& tokens)
{
    std::unique_ptr<FeatureToTokensMap> out_tokens(
        base::MakeUnique<FeatureToTokensMap>());
    if (!base::FeatureList::IsEnabled(features::kOriginTrials))
        return out_tokens;

    if (!IsOriginSecure(origin.GetURL()))
        return out_tokens;

    for (const auto& feature : tokens) {
        for (const std::string& token : feature.second) {
            std::string token_feature;
            if (TrialTokenValidator::ValidateToken(token, origin, &token_feature) == blink::WebOriginTrialTokenStatus::Success) {
                DCHECK_EQ(token_feature, feature.first);
                (*out_tokens)[feature.first].push_back(token);
            }
        }
    }
    return out_tokens;
}

} // namespace content
